//
//  ViewController.swift
//  KingfisherDemo
//
//  Created by 小冲冲 on 2023/8/12.
//

import UIKit
import Kingfisher
import Toast_Swift

class ViewController: BaseViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Kingfisher"
        self.view.backgroundColor = #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)
        
        self.headDataArray = ["一、基本的使用", "二、KingfisherOptionsInfoItem的使用", "三、下载器：ImageDownloader和KingfisherManager.shared.downloader", "四、ImageDataProvider以其它形式加载图片", "五、Cache缓存", "六、MemoryStorage缓存\n · totalCostLimit：内存缓存的最大容量，ImageCache.default中提供的默认值是设备物理内存的四分之一\n · countLimit: 内存缓存的最大长度(图片的数量)，默认max\n · expiration: 内存缓存的的过期时长：默认5分钟\n · cleanInterval: 清除过期缓存的时间间隔，默认2分钟", "七、DiskStorage缓存：Kingfisher中磁盘缓存是通过文件系统来实现的，也就是说每个缓存的数据都对应一个文件，其中Kingfisher把文件的创建时间修改为最后一次读取的时间，把文件的修改时间修改为过期时间。\n · sizeLimit: 磁盘缓存占用磁盘的最大值，为0时，表示不限制\n · expiration: 存活时间，默认7天\n · pathExtension: 文件的扩展名\n · usesHashedFileName：是否需要把文件名哈希\n · fileManager: 操作文件的FileManager\n · directory: 文件缓存所在的文件夹，默认在cache文件夹里", "八、内存或硬盘占据的大小以及清理", "九、Indicator加载指示器"]
        self.dataArray = [["常规加载图片(用直接设置的方法，Kingfisher首先会尝试从缓存中去取，如果没有，则直接下载图片并且缓存下来以备后用。此外，Kingfisher默认使用absoluteString of url(即绝对url）作为cacheKey以方便再次加载该图片的时候去缓存中根据cacheKey(也就是图片url）查找，通俗来说就是把图片的整个链接作为cacheKey来缓存在本地。)", "显示加载的进度"], ["originalCache：指定原始图片缓存", "targetCache：指定被处理后图片缓存", "downloader：指定用来下载图片的ImageDownloader对象", "transition：如果该图片是需要从网络下载的，Kingfisher会根据设置的ImageTransition枚举对象来对这个图片进行动画。如果是从缓存或者磁盘中找回的，则无动画。如果想要每次都执行动画，则需要设置.forceRefresh选项，使图片永远是下载的；或者使用.forceTransition，哪怕是从缓存中获取的图片，也会执行动画。", "downloadPriority：设置该图片下载的优先级，值的范围是0 - 1，如果没有设置这个选项，则默认使用URLSessionTask.defaultPriority", "forceRefresh：忽略本地缓存，重新下载", "fromMemoryCacheOrRefresh：如果设置了这个选项，Kingfisher会优先从缓存中找回图片，如果在缓存中没找到，则会直接开始重新下载，不会再去磁盘中进行查找。当我们的图片资源发生变化，但是图片的url并没有发生变化时，可使用该选项，能有效防止多次重复下载。", "forceTransition：每次都要执行翻转动画", "cacheMemoryOnly：只将图片存储在缓存中", "waitForCache：等待缓存结束后在执行completionBlock", "onlyFromCache：强制从缓存中获取图片（缓存中没有也不会从网络下载）", "backgroundDecode：在使用之前在后台线程解码图像", "callbackQueue：是一个用于指定回调闭包执行的队列。它定义了在完成图片加载或其他操作后，回调闭包在哪个队列中执行。默认情况下，Kingfisher 会在主队列（Main Queue）中执行回调闭包，以确保在主线程上更新 UI。但是，您可以通过设置 `callbackQueue` 来自定义回调闭包的执行队列。", "scaleFactor：将检索到的图片数据转换成一个图时 这个成员变量将被用作图片缩放因子。图像分辨率,而不是屏幕尺寸。你可能处理时需要指定正确的缩放因子@2x或@3x Retina图像。", "preloadAllAnimationData：整个的动图data是否被预加载。默认是不预加载整个的动图数据的，只是按需加载下一帧；如果设置为true，则会将整个动图的data加载并解码到内存中", "requestModifier：发送请求之前用于改变请求。这是最后的机会你可以修改请求。您可以修改请求一些定制的目的,如添加身份验证令牌头,进行基本的HTTP身份验证或类似的url映射。原始请求默认情况下将没有任何修改", "redirectHandler：用来在重定向之前去设置request，比如：我们可以在这里为header添加auth token，url映射等", "processor：处理器标识符（使用处理器处理图片时，把这个标识传给它）,标识会用来给 key 和 processor 的组合产生一个对应的key）", "cacheSerializer： 缓存序列化", "imageModifier：在图像加载和显示过程中，将使用你自定义的imageModifier来修改图像。你可以在modify(_:)方法中实现任何你需要的图像修改逻辑，例如调整大小、添加滤镜等操作。", "keepCurrentImageWhileLoading：在设置其它图片的过程中（如下载），保留当前图片。设置了这个属性之后，站位图会被忽略。", "onlyLoadFirstFrame：onlyLoadFirstFrame：在加载动图时，只加载第一帧。因为加载动图时比较耗费内存的，所以可以加载第一帧作为预览图片。如果不是加载动图，这个option设置了也会被忽略", "cacheOriginalImage：缓存原始图片", "onFailureImage：关联属性是个image，当下载或者从缓存中找回图片失败时，会使用这个image填充到ImageView中。适用于不想设置站位图，但是加载图片失败了，又想展示一个设定的图片的情况。", "alsoPrefetchToMemory：如果使用了ImagePrefetcher，预加载系统会主动将图片加载到内存中。预加载时如果图片已经被存放在磁盘中，从磁盘获取的图片也要缓存到内存中", "loadDiskFileSynchronously：同步加载磁盘图片文件。原本地盘加载图片在自己的异步队列中进行，但是如果imageView已经有图片了，再从磁盘读取image并设置给imageView，会导致闪烁。设置这个option，通常会让所有的加载在UI线程中进行，能防止产生闪烁，但会消耗性能。", "diskStoreWriteOptions：在将data写入磁盘存储时，关联对象用来设置写入选项", "memoryCacheExpiration：设置【内存】缓存过期时间", "memoryCacheAccessExtendingExpiration：当从内存、磁盘中取图片时，往后延长过期时间的策略，有以下几个值：.none,.cacheTime,.expirationTime(_ expiration: StorageExpiration), \n.none保持原来的过期时间, \n.cacheTime, \n设置过期时间为当前时间加上原来的过期时间, \n.expirationTime(_ expiration: StorageExpiration) 设置过期时间到指定时", "diskCacheExpiration：设置【硬盘】缓存过期时间", "diskCacheAccessExtendingExpiration：当从硬盘中取图片时，往后延长过期时间的策略，有以下几个值：.none,.cacheTime,.expirationTime(_ expiration: StorageExpiration), \n.none保持原来的过期时间, \n.cacheTime, \n设置过期时间为当前时间加上原来的过期时间, \n.expirationTime(_ expiration: StorageExpiration) 设置过期时间到指定时", "processingQueue：指定处理图片的队列。默认情况下，Kingfisher使用预定义的串行队列来处理图片。比如：使用.mainCurrentOrAsync来处理图片，能防止设置闪烁，但是如果处理图片耗时较长，会阻塞主线程UI。", "progressiveJPEG：支持渐进式图片，关联值为针对渐进式图片的处理。", "alternativeSources：当加载失败时，可供替代的数据源，当初始请求失败，则会根据关联值[Source]开始新的加载请求。如果某一个请求成功，则不会继续请求后面的Source；如果所有的备用Source都请求失败了，则会抛出错误", "retryStrategy：设置重试策略。当通过KingfisherManager找回图片时，如果发生了错误，会使用关联值进行重试。UIImageView和UIButton通过kf调用的的扩展方法都是通过KingfisherManager进行的，所以重试策略在这两个情况下可以生效。但是重试策略不支持ImageDownloader或者ImageCache。", "lowDataMode：设置低数据模式。当用户打开了低数据模式并且初始请求报错：NSURLErrorNetworkUnavailableReason.constrained，此时如果设置了这个option，关联值Source将会被用于进行低数据模式的请求，此外，可以让服务端提供低像素的图片来作为低数据模式获取的资源。"], ["ImageDownloader.default.downloadImage下载图片(不会存储在内存和硬盘)", "ImageDownloader.default.downloadImage下载图片并使用ImageCache.default.store进行缓存", "ImageCache.default.retrieveImage(forKey: url.absoluteString)根据key获取图片", "KingfisherManager.shared.downloader下载图片", "KingfisherManager.shared.cache.retrieveImage(forKey: url.absoluteString)根据key获取图片"], ["从本地url获取图片", "从base64获取图片", "从Video URL或AVAsset的指定时间生成一张图片", "使用Processor和CacheSerializer扩展Kingfisher，使Kingfisher支持webP格式的图片，建议使用https://github.com/yeatse/KingfisherWebP"], ["指定图片的cacheKey(KF.ImageResource(downloadURL: url, cacheKey: \"custom_cache_key\")仅仅是针对这一个图片，使用cacheKey来替代url来生成md5)", "检查上面方法的图片是否被缓存(内存或者硬盘中是否有一个缓存)", "内存中是否缓存ImageCache.default.memoryStorage.isCached(forKey: cacheKey)", "硬盘中是否缓存ImageCache.default.diskStorage.isCached(forKey: cacheKey)", "检查图片被缓存的位置", "从缓存中找回图片", "经过处理的图片加载并缓存", "从缓存中找到经过处理的图片", "从内存中获取图片retrieveImageInMemoryCache(forKey: )", "从硬盘中获取图片ImageCache.default.retrieveImageInDiskCache(forKey: )", "删除指定cacheKey的图片(默认:fromMemory: true, fromDisk: true)内存和硬盘中都会删除"], ["设置内存缓存配置MemoryStorage.Config", "获取内存缓存配置MemoryStorage.Config"], ["设置硬盘缓存配置DiskStorage.Config", "获取硬盘缓存配置DiskStorage.Config"], ["获取当前磁盘已经缓存大小", "清除内存缓存", "清除硬盘缓存(异步操作)", "清除过期或超过磁盘缓存大小的缓存（异步操作）\n提示：当你的app接收到内存警告（memory warning)的时候，Kingfisher会净化内存缓存，也就是说，在需要的时候，Kingfisher会主动清理一些已过期或者超过缓存尺寸大小的缓存，因此一般而言，没有必要自己手动去清理缓存，而这些清理缓存方法的存在主要在于以防你想要用户有更多对缓存进行操作的情况。比如有时候，有部分app习惯在设置里面加一个清理缓存的交互，为了方便，你可以根据需要调用这些手动清理缓存的方法。"], ["使用gif作为指示器"]]
    }
}


//MARK: - 九、Indicator加载指示器
extension ViewController {
    //MARK: 9.01、使用gif作为指示器
    @objc func test901() {
        guard let path = Bundle.main.path(forResource: "pika4", ofType: "gif") else {
            debugPrint("❌ 路径不对")
            self.toastMessage("❌ 路径不对")
            return
        }
        
        let data = try! Data(contentsOf: URL(fileURLWithPath: path))

        let imageView = CustomImageView()
        imageView.kf.indicatorType = .image(imageData: data)
        self.view.addSubview(imageView)
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        imageView.kf.setImage(with: URL(string: "https://picsum.photos/id/247/600/600"), placeholder: UIImage.jk.image(color: UIColor.randomColor)) { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
}

//MARK: - 八、内存或硬盘占据的大小以及清理
extension ViewController {
    
    //MARK: 8.04、清除过期或超过磁盘缓存大小的缓存（异步操作）
    @objc func test804() {
        ImageCache.default.cleanExpiredDiskCache {
            self.toastMessage("✅：清除过期或超过磁盘缓存大小的缓存")
        }
    }
    
    //MARK: 8.03、清除硬盘缓存(异步操作)
    @objc func test803() {
        ImageCache.default.clearDiskCache {
            self.toastMessage("✅：清除硬盘缓存")
        }
    }
    
    //MARK: 8.02、清除内存缓存
    @objc func test802() {
        ImageCache.default.clearMemoryCache()
        self.toastMessage("✅：清除内存缓存")
    }
    
    //MARK: 8.01、获取当前磁盘已经缓存大小
    @objc func test801() {
        ImageCache.default.calculateDiskStorageSize { result in
            switch result {
            case .success(let diskSize):
                debugPrint("获取当前磁盘缓存大小：\(diskSize.jk.uintToInt.jk.covertUInt64ToString())")
            case .failure(let error):
                debugPrint("error: \(error)")
            }
        }
    }
}

//MARK: - 七、DiskStorage缓存：Kingfisher中磁盘缓存是通过文件系统来实现的，也就是说每个缓存的数据都对应一个文件，其中Kingfisher把文件的创建时间修改为最后一次读取的时间，把文件的修改时间修改为过期时间。
/**
 public struct Config {
     /// 磁盘缓存占用磁盘的最大值，为0z时，表示不限制
     public var sizeLimit: UInt
     /// 存活时间，默认7天
     public var expiration: StorageExpiration = .days(7)
     /// 文件的扩展名
     public var pathExtension: String? = nil
     /// 是否需要把文件名哈希
     public var usesHashedFileName = true
     /// 操作文件的FileManager
     let fileManager: FileManager
     /// 文件缓存所在的文件夹，默认在cache文件夹里
     let directory: URL?
 }
 */
extension ViewController {
    
    //MARK: 7.02、获取硬盘缓存配置DiskStorage.Config
    @objc func test702() {
        // 磁盘缓存占用磁盘的最大值，为0时，表示不限制
        let config = ImageCache.default.diskStorage.config
        debugPrint("磁盘缓存占用磁盘的最大存储大小为：\(config.sizeLimit.jk.uintToInt.jk.covertUInt64ToString())", "磁盘缓存的的过期时长：\(config.expiration)秒", separator: "\n")
    }
    
    //MARK: 7.01、设置硬盘缓存配置DiskStorage.Config
    @objc func test701() {
        // 磁盘缓存占用磁盘的最大值，为0z时，表示不限制
        var config = ImageCache.default.diskStorage.config
        // 磁盘缓存的的过期时长：默认7天，磁盘缓存永不过期：。never
        config.expiration = .days(7)
        // 是否需要把文件名哈希
        config.usesHashedFileName = true
        // 设置磁盘缓存内存最大1GB
        config.sizeLimit = 1024 * 1024 * 1024
        ImageCache.default.diskStorage.config = config
        
        self.toastMessage("✅ 设置成功")
    }
}

//MARK: - 六、MemoryStorage缓存
/**
 public struct Config {
     /// 内存缓存的最大容量，ImageCache.default中提供的默认值是设备物理内存的四分之一
     public var totalCostLimit: Int
     /// 内存缓存的最大长度(图片的数量)，默认max
     public var countLimit: Int = .max
     /// 内存缓存的的过期时长：默认5分钟
     public var expiration: StorageExpiration = .seconds(300)
     /// 清除过期缓存的时间间隔，默认2分钟
     public let cleanInterval: TimeInterval
 }
 */
extension ViewController {
    
    //MARK: 6.02、获取内存缓存配置MemoryStorage.Config
    @objc func test602() {
        let config = ImageCache.default.memoryStorage.config
        debugPrint("缓存内存最大小为：\(config.totalCostLimit.jk.covertUInt64ToString())", "内存缓存的最大长度(图片的数量)：\(config.countLimit)", "内存缓存的的过期时长：\(config.expiration)", "秒清除过期缓存的时间间隔：\(config.cleanInterval)秒", separator: "\n")
    }
    
    //MARK: 6.01、设置内存缓存配置MemoryStorage.Config
    @objc func test601() {
        // 设置缓存内存最大小为：300 MB，默认是0，表示无限制
        var config = MemoryStorage.Config(totalCostLimit: 300 * 1024 * 1024)
        /// 内存缓存的最大长度(图片的数量)
        config.countLimit = 100
        // 内存缓存的的过期时长：默认5分钟
        config.expiration = .seconds(300)
        // 清除过期缓存的时间间隔，默认2分钟
        config.cleanInterval = 120
        ImageCache.default.memoryStorage.config = config
        
        self.toastMessage("✅ 设置成功")
    }
}

//MARK: - 五、Cache缓存
extension ViewController {
    //MARK: 5.11、删除指定cacheKey的图片(默认:fromMemory: true, fromDisk: true)内存和硬盘中都会删除
    @objc func test511() {
        let cacheKey = "custom_cache_key"
        let cached = ImageCache.default.isCached(forKey: cacheKey)
        guard cached else {
            debugPrint("❌：没有缓存。。。。。。")
            return
        }
        // 删除指定键的图片
        ImageCache.default.removeImage(forKey: cacheKey) {
            debugPrint("当前的线程：\(Thread.current)")
            self.toastMessage("✅ 删除成功")
        }
    }
    //MARK: 5.10、从硬盘中获取图片ImageCache.default.retrieveImageInDiskCache(forKey: )
    @objc func test510() {
        let cacheKey = "custom_cache_key"
        let cached = ImageCache.default.diskStorage.isCached(forKey: cacheKey)
        guard cached else {
            debugPrint("❌：硬盘中没有缓存。。。。。。")
            self.toastMessage("❌：先点击4.1进行图片缓存")
            return
        }
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
    
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        ImageCache.default.retrieveImageInDiskCache(forKey: cacheKey) { result in
            switch result {
            case .success(let image):
                if let weakImage = image {
                    debugPrint("✅：\(weakImage) 当前线程：\(Thread.current)")
                    DispatchQueue.main.async {
                        imageView.image = weakImage
                        JKAsyncs.asyncDelay(3) {
                        } _: {
                            imageView.removeFromSuperview()
                        }
                    }
                }
            case .failure(let error):
                debugPrint("❌：\(error)")
            }
        }
    }

    //MARK: 5.09、从内存中获取图片retrieveImageInMemoryCache(forKey: )
    @objc func test509() {
        let cacheKey = "custom_cache_key"
        let cached = ImageCache.default.memoryStorage.isCached(forKey: cacheKey)
        guard cached else {
            debugPrint("❌：内存中没有缓存。。。。。。")
            self.toastMessage("❌：先点击4.1进行图片缓存")
            return
        }
        guard let result = ImageCache.default.retrieveImageInMemoryCache(forKey: cacheKey) else {
            return
        }
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
    
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        imageView.image = result
        debugPrint("✅：内存中缓存数据： \(result)")
        JKAsyncs.asyncDelay(3) {
        } _: {
            imageView.removeFromSuperview()
        }
    }
    
    //MARK: 5.08、从缓存中找到经过处理的图片
    @objc func test508() {
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
    
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }

        let processor = RoundCornerImageProcessor(cornerRadius: 50)
        let cacheKey = "custom_cache_key1"
        let cached = ImageCache.default.isCached(forKey: cacheKey, processorIdentifier: processor.identifier)
        guard cached else {
            debugPrint("❌：没有缓存。。。。。。")
            self.toastMessage("❌：没有缓存。。。。。。")
            return
        }
        ImageCache.default.retrieveImage(forKey: cacheKey, options: [.processor(processor)]) { result in
            JKAsyncs.asyncDelay(3) {
            } _: {
                imageView.removeFromSuperview()
            }
            switch result {
            case .success(let value):
                imageView.image = value.image!
                debugPrint("✅ 有缓存 cacheType:\(value.cacheType) image:\(value.image ?? UIImage())")
            case .failure(let error):
                debugPrint("❌：\(error)")
            }
        }
    }
    
    //MARK: 5.07、经过处理的图片加载并缓存
    @objc func test507() {
        guard let url =  URL(string: "https://picsum.photos/id/240/600/600") else {
            return
        }
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
    
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        let cacheKey = "custom_cache_key1"
        let resource = KF.ImageResource(downloadURL: url, cacheKey: cacheKey)
        
        let processor = RoundCornerImageProcessor(cornerRadius: 50)
  
        imageView1.kf.setImage(with: resource, placeholder: UIImage.jk.image(color: UIColor.randomColor), options: [.processor(processor)]) {[weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 5.06、从缓存中找回图片
    @objc func test506() {
        // 如果不是使用指定的cacheKey，那就使用 url.absoluteString
        let cacheKey = "custom_cache_key"
        let cached = ImageCache.default.isCached(forKey: cacheKey)
        guard cached else {
            debugPrint("❌：没有缓存。。。。。。")
            self.toastMessage("❌：没有缓存。。。。。。")
            return
        }
        ImageCache.default.retrieveImage(forKey: cacheKey) { result in
            switch result {
            case .success(let value):
                debugPrint("✅ 有缓存 cacheType:\(value.cacheType) image:\(value.image ?? UIImage())")
            case .failure(let error):
                debugPrint("❌：\(error)")
            }
        }
    }
    
    //MARK: 5.05、检查图片被缓存的位置
    @objc func test505() {
        // 如果不是使用指定的cacheKey，那就使用 url.absoluteString
        let cacheKey = "custom_cache_key"
        let cached = ImageCache.default.isCached(forKey: cacheKey)
        guard cached else {
            debugPrint("❌：没有缓存。。。。。。")
            self.toastMessage("❌：没有缓存。。。。。。")
            return
        }
        let cacheType = ImageCache.default.imageCachedType(forKey: cacheKey)
        debugPrint("✅ 有缓存 缓存类型：\(cacheType)")
    }
    
    //MARK: 5.04、硬盘中是否缓存ImageCache.default.diskStorage.isCached(forKey: cacheKey)
    @objc func test504() {
        let cacheKey = "custom_cache_key"
        let cached = ImageCache.default.diskStorage.isCached(forKey: cacheKey)
        guard cached else {
            debugPrint("❌：硬盘中没有缓存。。。。。。")
            self.toastMessage("❌：硬盘中没有缓存")
            return
        }
        debugPrint("✅：硬盘中有缓存")
        self.view.makeToast("✅：硬盘中有缓存", position: .center)
    }
    
    //MARK: 5.03、内存中是否缓存ImageCache.default.memoryStorage.isCached(forKey: cacheKey)
    @objc func test503() {
        let cacheKey = "custom_cache_key"
        let cached = ImageCache.default.memoryStorage.isCached(forKey: cacheKey)
        guard cached else {
            debugPrint("❌：内存中没有缓存。。。。。。")
            self.toastMessage("❌：内存中没有缓存")
            return
        }
        debugPrint("✅：内存中有缓存")
        self.view.makeToast("✅：内存中有缓存", position: .center)
    }

    //MARK: 5.02、检查上面方法的图片是否被缓存(内存或者硬盘中是否有一个缓存)
    @objc func test502() {
        // 如果不是使用指定的cacheKey，那就使用 url.absoluteString
        let cacheKey = "custom_cache_key"
        let cached = ImageCache.default.isCached(forKey: cacheKey)
        guard cached else {
            self.toastMessage("❌：没有缓存")
            return
        }
        debugPrint("✅ 有缓存。。。。。。")
        self.toastMessage("✅ 有缓存")
    }
    
    //MARK: 5.01、指定图片的cacheKey(KF.ImageResource(downloadURL: url, cacheKey: "custom_cache_key")仅仅是针对这一个图片，使用cacheKey来替代url来生成md5)
    @objc func test501() {
        guard let url =  URL(string: "https://picsum.photos/id/239/600/600") else {
            return
        }
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
    
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let resource = KF.ImageResource(downloadURL: url, cacheKey: "custom_cache_key")
        imageView1.kf.setImage(with: resource, placeholder: UIImage.jk.image(color: UIColor.randomColor)) { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
}

//MARK: - 四、ImageDataProvider - 以其它形式加载图片
extension ViewController {
    
    //MARK: 4.04、使用Processor和CacheSerializer扩展Kingfisher，使Kingfisher支持webP格式的图片，建议使用https://github.com/yeatse/KingfisherWebP
    @objc func test404() {
        guard let url = URL(string:"https://www.gstatic.com/webp/gallery/5.webp") else { return }
        /*
        1. https://www.gstatic.com/webp/gallery/1.webp
        2. https://www.gstatic.com/webp/gallery/2.webp
        3. https://www.gstatic.com/webp/gallery/3.webp
        4. https://www.gstatic.com/webp/gallery/4.webp
        5. https://www.gstatic.com/webp/gallery/5.webp
         */
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        imageView.kf.setImage(with: url, placeholder: UIImage.jk.image(color: UIColor.randomColor), options: [.processor(WebPProcessor.default), .cacheSerializer(WebPCacheSerializer.default)]) { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 4.03、从Video URL或AVAsset的指定时间生成一张图片
    @objc func test403() {
        guard let url = URL(string: "https://img.niucache.com/video/static/upload/video/1699003305.780119.mp4") else { return }
        let provider = AVAssetImageDataProvider(
            assetURL: url,
            seconds: 2.0
        )
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
    
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        imageView.kf.setImage(with: provider, placeholder: UIImage.jk.image(color: UIColor.randomColor)) { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 4.02、从base64获取图片
    @objc func test402() {
        let testImageString =
            "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAD8GlDQ1BJQ0MgUHJvZmlsZQAAOI2NVd1v21QUP4lvXKQWP6Cxjg4Vi69VU1u5GxqtxgZJk6XpQhq5zdgqpMl1bhpT1za2021V" +
            "n/YCbwz4A4CyBx6QeEIaDMT2su0BtElTQRXVJKQ9dNpAaJP2gqpwrq9Tu13GuJGvfznndz7v0TVAx1ea45hJGWDe8l01n5GPn5iWO1YhCc9BJ/RAp6Z7TrpcLgIuxoVH1sNfIcHeNwfa6/9z" +
            "dVappwMknkJsVz19HvFpgJSpO64PIN5G+fAp30Hc8TziHS4miFhheJbjLMMzHB8POFPqKGKWi6TXtSriJcT9MzH5bAzzHIK1I08t6hq6zHpRdu2aYdJYuk9Q/881bzZa8Xrx6fLmJo/iu4/V" +
            "XnfH1BB/rmu5ScQvI77m+BkmfxXxvcZcJY14L0DymZp7pML5yTcW61PvIN6JuGr4halQvmjNlCa4bXJ5zj6qhpxrujeKPYMXEd+q00KR5yNAlWZzrF+Ie+uNsdC/MO4tTOZafhbroyXuR3Df" +
            "08bLiHsQf+ja6gTPWVimZl7l/oUrjl8OcxDWLbNU5D6JRL2gxkDu16fGuC054OMhclsyXTOOFEL+kmMGs4i5kfNuQ62EnBuam8tzP+Q+tSqhz9SuqpZlvR1EfBiOJTSgYMMM7jpYsAEyqJCH" +
            "DL4dcFFTAwNMlFDUUpQYiadhDmXteeWAw3HEmA2s15k1RmnP4RHuhBybdBOF7MfnICmSQ2SYjIBM3iRvkcMki9IRcnDTthyLz2Ld2fTzPjTQK+Mdg8y5nkZfFO+se9LQr3/09xZr+5GcaSuf" +
            "eAfAww60mAPx+q8u/bAr8rFCLrx7s+vqEkw8qb+p26n11Aruq6m1iJH6PbWGv1VIY25mkNE8PkaQhxfLIF7DZXx80HD/A3l2jLclYs061xNpWCfoB6WHJTjbH0mV35Q/lRXlC+W8cndbl9t2" +
            "SfhU+Fb4UfhO+F74GWThknBZ+Em4InwjXIyd1ePnY/Psg3pb1TJNu15TMKWMtFt6ScpKL0ivSMXIn9QtDUlj0h7U7N48t3i8eC0GnMC91dX2sTivgloDTgUVeEGHLTizbf5Da9JLhkhh29QO" +
            "s1luMcScmBXTIIt7xRFxSBxnuJWfuAd1I7jntkyd/pgKaIwVr3MgmDo2q8x6IdB5QH162mcX7ajtnHGN2bov71OU1+U0fqqoXLD0wX5ZM005UHmySz3qLtDqILDvIL+iH6jB9y2x83ok898G" +
            "OPQX3lk3Itl0A+BrD6D7tUjWh3fis58BXDigN9yF8M5PJH4B8Gr79/F/XRm8m241mw/wvur4BGDj42bzn+Vmc+NL9L8GcMn8F1kAcXgSteGGAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlU" +
            "WHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9" +
            "Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJo" +
            "dHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8" +
            "L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAKZklEQVR4Ae2ax28VyxLGywYMJuecgwgSIILIgg1pQRRJQrBkxZr9/RNYAhJiA0gEIbIE6JEzIggQIKLJOefod351+fzmzps5njke3wV2" +
            "SeM+Mx2qvq+qq3t6XNS1a9fyHz9+WE2V4poMHqcX11TPC3ctAWKippa1EVBTPS/cNT4C6oqJf7MsKiqKVVdeXh5bVx0V/woBcYCDYNVGpcAG2+hZlmW1EgAYrl+/ftnPnz+NTdenT5/s8+fP" +
            "sRgaN25sDRo0sLp161pxcbFfkFBdRFQLAQIO6G/fvtmHDx8cwMCBA61Pnz7WqVMna9GihQG2fv36Tsj79+/t5cuXdu/ePbt165ZdunTJGjVqZKWlpVZSUuJEQGTWkjkBeA1D8fKXL1+sd+/e" +
            "Nnr0aBs8eLADLqlfYqUNSq1evXru5Tp16nh0fP/+3cmiD6S9fv3azp07Z8eOHbNHjx45GZCFZBkNRR07dsws6wAe4wHfrVs3mzp1quH1Jk2aOHgig6iAIIU1pSJGIU9Ju48fPzoRZ86csT17" +
            "9tiLFy98LEjLKhoyIwCjAY7hs2fPtgkTJljLli09xAHJ/BdYvAjooFAnUTvyAO2IiocPHzoJu3fv9unDtMiChEwIwCPM39yrtc2ZM8dGjBhRARxCkDBggc1XihTGpz+55MCBA7ZlyxYnhRyi" +
            "8fONk6+uTi48/8rXoLI6jMM7Q4cOtUWLFnmJ5zBMniwEPHrpx4WnuVgdevToYW3atLGysjJ79eqVJ0kRVZmtUfVVIoCwx/NDhgxx8P369XMdGCvjo5SmfaaxGBdyO3fubK1bt7YbN24YqwfT" +
            "oVASCt4KA565iTHz5s3zbC9PAVC/CzVMJNFfY/GMyCLqhg0bZnPnznXg1ENSIVIQASjDMLIyCa9///5+L7AYyPpNyPKb56qTkXoWLFVHqed4nHEYD9IRSGAZZXmdNWuWL5PoKUQKmgIoe/Dg" +
            "gS1YsMCmTJnixikZUUeyun//vpcAYEODKC/wGzBctFeICzQlAKl7+/atPXnyxMnWpoh6xuKefMCe4erVq75EUpdGUhOAsWxa2rdvbwsXLrQOHTrY169fK7wDqPPnz9uyZcvs9u3bvi8ACBm7" +
            "YcOGDgwD6cPFNNK5JBsdLtozt69fv24HDx609evX27p162zcuHEOGPDooWzatKkTcfToUS/TEpB6J4hxZH3mfW4T5WRoSuBtDGcri5AgV61a5XmC+dqzZ08n5N27d/bmzRvf6AAeb0MQ22MA" +
            "IRcvXrQ1a9b4b8hG2BF2797dCWLeIwBmtzljxgzbsGGDL8Ui1BtU8icVAQDFY2T7AQMGeGiz+cEbGEI9Xn327Jk1a9bMM3aXLl382fLly313ePfu3bwmQQAXUwyCIZyxIQnSAAfRiKKRDRc7" +
            "zu3bt3vClEPyKvpdmYoAFON9Eg/Gse1FWVgwGA+pxHhWC8jhRUigwv0YC4CENsQFsz/TjvHCQh/ad89FxsSJE23Hjh2poiDVKqCwwzj29ygOEkA9iYn3AKaC6kQEwBGBpH/w4jl9NL/DgNu2" +
            "beuRgB6NTUk/6pgKaSUxARjPzmvs2LGeiGRsUCHGcImoYB2/ARQGlaZN+W/g6IgahxWBPKNpGR476j4VAbyd9erVy1cAPBclzF0yd7t27WKJiOoX9wygACZB/mf/fl8OiZCwYA/TC/ueP39e" +
            "sSqF24Xv/3+kcIvf92K8VatWnvyCYUidVgBeVlgFmPfqEzNk4seMw9Q6fvy4nTp1ynNJMI8o6pgGEI9EkRSlMBEBKJDH2ZVFCW1Y3o4cOVJhRFS7Qp5BAICaN29up0+f9uQbBEg9F05gOUXi" +
            "pmFYfyIC6AQBzDFePMICeHICyx8rA4JBWQo6EPYW7A6D46uOZ0QekikBKIAA2GWnFlTOb9Uz95Cgd/xBBn/Qg4dZDknGwSnI8NhAG+xjhQrXx5mQOAIYkHkntqMGDBITVV/VZ9Id5V3ppo2W" +
            "2yT6EhOAV4mCKOUoQnHU9EhiRNI2gESPwjyqH/YpX0XVh58lIgDFsMrmJmr3p3q2pPyOIymsPM09wMkzgEcPDkEXQkk9gn3YGaz3ipg/iQigLwTw/q8kp/FQjAHMTyVJCJBBapdFiWeDJ8wa" +
            "UzYAmhyB8DuJJGoFQM0rXobiwLFE8uGjOiKAMQHF+Ngi7wNSEUCE4H0kUwIYUKBZhjjwQEHQCJSzSvDOTpukBjB2ZSIPs/wNHz7cT4iIBtlEf/Q9ffrUL+6TOiFRBDCgwo9DCt7LFRHUYciv" +
            "n3+f2g4aNMhGjhzpn7iySopML3TOnDnTX3jC5GMD9tCGXSg7wmohgJ3YyZMnPReEw7Co+O+TIrbKHH5wZlCWO7qGBAyGpKDHMDpO1BYdJD3OEIisadOm+TacaIsai43YnTt3fNucOQEYq7Dm" +
            "hYdpECZBcxHwS5Ys8a9DkMBBBgYJWBxwnqsN7TlXBDznD4sXL/aXnTAwdGIHmzCO4JDg1PQHef6kPhNkp8Uc50SIUCPryhuUMkjv55wY40VI43CTJKr9epRdhDHvFETb5MmT/eB1/PjxfvYI" +
            "eJGsvtxj05UrV2zt2rX+vSANAalOhDCAtzKUcQrL6yfzk+dBEuQl6iGCiJg+fbq/p1+4cMH27dvnXtNcpi9Ecmi6dOlSf6dnRYEEPoAgIlp6eAbQejn9bI05QxRxTJGkkjgJakCSIaD4SPn4" +
            "8WP3bphxjOTSnoGTYyKGwwoyOUdjAq9xiRLqOHOgLaSRTwAu8GobLItz4U/i4ygMwrEvjaQmQCF38+ZNO3TokBuN8WESMAISeI5HAHbixAnbuXOnL2NhT0IIxu/du9cuX75ccVwuMsOgiDIS" +
            "LEsfEQVJYVLDfaLuUxPAICiH7c2bN9vZs2cdYD7lqiNJ8ZEEwjRNZBT35AZyBO0Arn5qoxJSGYNEvD93SsTFQUha7zNeQQRgAAZi8NatW/0jZT6DUaRIIPzjhDEgAG/GCeOQ9WnDP05s27at" +
            "IkHG9cn3vCACGBCP4U2WObLvtWvXXE94aYxSDtA4oS6qHuBcJF2mFP8+s2nTpsQ64/SlWgXCgxByZGmmATJ//nxPYMxNjMRgyT9A/e+xqv9RBvtRAdlMB8Bz4su54MaNG31DxkeUQkJfCguO" +
            "AA2Acr4TkBRXrlzp/8HBcoTBzFMEQICghIicL9U9tlQf2rPOI+w/du3aZatXr/YPovo2ETtIgooqRYDGhwSM4U1sxYoV/lFz0qRJ/vWItV0AICXfksZ48jZRBHg8ztdfljrAE218dmNc2lZV" +
            "MiFAhgOUHHD48GFPUKNGjbIxY8Z4ksKjgCdv8DtKeM4YJEKWN/YRZbkcw0kzGyjq+T6AjizAY0Mm/yQVBKO5DliAMB369u3r3wY5UAEYkRAlAKQ/SyxE4XXeB9gRQoz2G3EERo1Z2bPMCZBC" +
            "QGIonoIMkqIiRG3iStrifaYB3hZhWQKX7symgAZUqRDFeIBzJQVAtlcCZbyk/aQ7TVltBMiIQo0vtJ/0Ji2jJ2PS3n9Au1oC/gAnVglCbQRUib4/oHNtBPwBTqwShGI2HTVZ/gvZ53KpZJXY" +
            "DwAAAABJRU5ErkJggg=="
        guard let testImageData = Data(base64Encoded: testImageString) else {
            debugPrint("❌ base64数据有问题")
            self.toastMessage("❌ base64数据有问题")
            return
        }
        let base64String = testImageData.base64EncodedString()
        let provider = Base64ImageDataProvider(base64String: base64String, cacheKey: "123")
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
    
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        imageView.kf.setImage(with: provider, placeholder: UIImage.jk.image(color: UIColor.randomColor)) { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 4.01、从本地url获取图片
    @objc func test401() {
        let customPath = "/path/to/image/cache"
        let url = URL(fileURLWithPath: customPath)
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
    
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        let provider = LocalFileImageDataProvider(fileURL: url)
        // 结合processor
        let processor = RoundCornerImageProcessor(cornerRadius: 20)
        
        imageView.kf.setImage(with: provider, placeholder: UIImage.jk.image(color: UIColor.randomColor), options: [.processor(processor)]) { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
}

//MARK: - 三、下载器：ImageDownloader和KingfisherManager.shared.downloader
extension ViewController {
    
    //MARK: 3.05、KingfisherManager.shared.cache.retrieveImage(forKey: url.absoluteString)根据key获取图片
    @objc func test305() {
        guard let url =  URL(string: "https://picsum.photos/id/238/600/600") else {
            return
        }
        
        KingfisherManager.shared.cache.retrieveImage(forKey: url.absoluteString) { result in
            switch result {
            case .success(let value):
                // 在这里使用缓存的图像
                // 获取图片的大小
                if let image = value.image {
                    let imageSize = image.size
                    debugPrint("Image size: \(imageSize)")
                }
                debugPrint("在这里使用缓存的图像 Retrieved image from cache: \(value.cacheType) absoluteString：\(url.absoluteString)")
            case .failure(let error):
                // 处理错误
                debugPrint("Error retrieving image from cache: \(error)")
            }
        }
    }
    
    //MARK: 3.04、KingfisherManager.shared.downloader下载图片
    @objc func test304() {
        guard let url =  URL(string: "https://picsum.photos/id/238/600/600") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
    
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        // 对某一个特定的下载设置超时时间
        let modifier = AnyModifier { request in
            var r = request
            r.timeoutInterval = 20
            return r
        }
   
        let options: KingfisherOptionsInfo = [
            .forceRefresh, // 强制从网络下载
            .cacheOriginalImage, // 缓存原始图片
            .requestModifier(modifier)
        ]
        view.makeToast("下载中...", position: .center)
        KingfisherManager.shared.downloader.downloadImage(with: url, options: options) { receivedSize, totalSize in
            let progress = Float(receivedSize) / Float(totalSize)
            debugPrint("下载进度：\(progress)")
        } completionHandler: {[weak self] result in
            guard let self else {
                return
            }
            self.view.hideToast()
            switch result {
            case .success(let imageResult):
                // 缓存图片
                // ImageCache.default.store(imageResult.image, forKey: url.absoluteString)
                debugPrint("下载成功：\(imageResult.image) url: \(imageResult.url!)")
                imageView.image = imageResult.image
                JKAsyncs.asyncDelay(3) {
                } _: {
                    imageView.removeFromSuperview()
                }
            case .failure(let error):
                debugPrint("下载失败：\(error)")
            }
        }
    }
    
    //MARK: 3.03、ImageCache.default.retrieveImage(forKey: url.absoluteString)根据key获取图片
    @objc func test303() {
        guard let url =  URL(string: "https://picsum.photos/id/237/200/300") else {
            return
        }
        ImageCache.default.retrieveImage(forKey: url.absoluteString) { result in
            switch result {
            case .success(let image):
                // 在这里使用缓存的图像
                debugPrint("在这里使用缓存的图像 Retrieved image from cache: \(image) absoluteString：\(url.absoluteString)")
            case .failure(let error):
                // 处理错误
                debugPrint("Error retrieving image from cache: \(error)")
            }
        }
    }
    
    //MARK: 3.02、ImageDownloader.default.downloadImage下载图片并使用ImageCache.default.store进行缓存
    @objc func test302() {
        guard let url =  URL(string: "https://picsum.photos/id/237/200/300") else {
            return
        }
        
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }

        view.makeToast("加载中...", position: .center)
        ImageDownloader.default.downloadImage(with: url) { receivedSize, totalSize in
            let progress = Float(receivedSize) / Float(totalSize)
            debugPrint("下载进度：\(progress)")
        } completionHandler: {[weak self] result in
            guard let self else {
                return
            }
            self.view.hideToast()
            switch result {
            case .success(let imageResult):
                debugPrint("下载成功，缓存：\(imageResult.image) url: \(imageResult.url!)")
                ImageCache.default.store(imageResult.image, forKey: url.absoluteString)
                imageView1.image = imageResult.image
                JKAsyncs.asyncDelay(3) {
                } _: {
                    imageView1.removeFromSuperview()
                }
            case .failure(let error):
                debugPrint("下载失败：\(error)")
            }
        }
    }
    
    //MARK: 3.01、ImageDownloader.default.downloadImage下载图片(不会存储在内存和硬盘)
    @objc func test301() {
        guard let url =  URL(string: "https://picsum.photos/id/237/200/300") else {
            return
        }
        
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }

        view.makeToast("加载中...", position: .center)
        ImageDownloader.default.downloadImage(with: url) { receivedSize, totalSize in
            let progress = Float(receivedSize) / Float(totalSize)
            debugPrint("下载进度：\(progress)")
        } completionHandler: {[weak self] result in
            guard let self else {
                return
            }
            self.view.hideToast()
            switch result {
            case .success(let imageResult):
                debugPrint("下载成功，缓存：\(imageResult.image) url: \(imageResult.url!)")
                imageView1.image = imageResult.image
                JKAsyncs.asyncDelay(3) {
                } _: {
                    imageView1.removeFromSuperview()
                }
            case .failure(let error):
                debugPrint("下载失败：\(error)")
            }
        }
    }
}
                          
//MARK: - 二、KingfisherOptionsInfoItem的使用
extension ViewController {
    
    //MARK: 2.36、lowDataMode：设置低数据模式。当用户打开了低数据模式并且初始请求报错：NSURLErrorNetworkUnavailableReason.constrained，此时如果设置了这个option，关联值Source将会被用于进行低数据模式的请求，此外，可以让服务端提供低像素的图片来作为低数据模式获取的资源。
    /// lowDataMode：设置低数据模式。当用户打开了低数据模式并且初始请求报错：NSURLErrorNetworkUnavailableReason.constrained，此时如果设置了这个option，关联值Source将会被用于进行低数据模式的请求，此外，可以让服务端提供低像素的图片来作为低数据模式获取的资源。
    @objc func test236() {
        guard let url = URL(string: "https://picsum.photo"), let backupURL = URL(string: "https://picsum.photos/id/79/300/300") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        // 创建一个备用 URL
        let source = Source.network(backupURL)

        let options: KingfisherOptionsInfo = [
            .lowDataMode(source)
        ]
        
        imageView.kf.setImage(with: url, placeholder: UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.35、retryStrategy：设置重试策略。当通过KingfisherManager找回图片时，如果发生了错误，会使用关联值进行重试。UIImageView和UIButton通过kf调用的的扩展方法都是通过KingfisherManager进行的，所以重试策略在这两个情况下可以生效。但是重试策略不支持ImageDownloader或者ImageCache。
    /// retryStrategy：设置重试策略。当通过KingfisherManager找回图片时，如果发生了错误，会使用关联值进行重试。UIImageView和UIButton通过kf调用的的扩展方法都是通过KingfisherManager进行的，所以重试策略在这两个情况下可以生效。但是重试策略不支持ImageDownloader或者ImageCache。
    @objc func test235() {
        guard let url = URL(string: "https://picsum.photo") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        // 最多重试2次，每次间隔3秒
        let retry = DelayRetryStrategy(maxRetryCount: 2, retryInterval: .seconds(3))

        let options: KingfisherOptionsInfo = [
            .retryStrategy(retry)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.34、alternativeSources：当加载失败时，可供替代的数据源，当初始请求失败，则会根据关联值[Source]开始新的加载请求。如果某一个请求成功，则不会继续请求后面的Source；如果所有的备用Source都请求失败了，则会抛出错误
    /// alternativeSources：当加载失败时，可供替代的数据源，当初始请求失败，则会根据关联值[Source]开始新的加载请求。如果某一个请求成功，则不会继续请求后面的Source；如果所有的备用Source都请求失败了，则会抛出错误
    @objc func test234() {
        guard let url = URL(string: "https://picsum.photos/id/77/800/800"), let backupURL = URL(string: "https://picsum.photos/id/78/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        // 创建一个备用 URL
        let source = Source.network(backupURL)
        let options: KingfisherOptionsInfo = [
            .alternativeSources([source])
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.33、progressiveJPEG：支持渐进式图片，关联值为针对渐进式图片的处理。
    /// progressiveJPEG：支持渐进式图片，关联值为针对渐进式图片的处理。
    @objc func test233() {
        guard let url = URL(string: "https://picsum.photos/id/72/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
       
        let options: KingfisherOptionsInfo = [
            .progressiveJPEG(ImageProgressive(isBlur: true, isFastestScan: true, scanInterval: 1.0))
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.32、processingQueue：指定处理图片的队列。默认情况下，Kingfisher使用预定义的串行队列来处理图片。比如：使用.mainCurrentOrAsync来处理图片，能防止设置闪烁，但是如果处理图片耗时较长，会阻塞主线程UI。
    /// processingQueue：指定处理图片的队列。默认情况下，Kingfisher使用预定义的串行队列来处理图片。比如：使用.mainCurrentOrAsync来处理图片，能防止设置闪烁，但是如果处理图片耗时较长，会阻塞主线程UI。
    @objc func test232() {
        guard let url = URL(string: "https://picsum.photos/id/71/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
       
        let options: KingfisherOptionsInfo = [
            .processingQueue(CallbackQueue.mainAsync)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.31、diskCacheAccessExtendingExpiration：当从硬盘中取图片时，往后延长过期时间的策略，有以下几个值：.none,.cacheTime,.expirationTime(_ expiration: StorageExpiration), \n.none保持原来的过期时间, \n.cacheTime, \n设置过期时间为当前时间加上原来的过期时间, \n.expirationTime(_ expiration: StorageExpiration) 设置过期时间到指定时
    /// diskCacheAccessExtendingExpiration：当从硬盘中取图片时，往后延长过期时间的策略，有以下几个值：.none,.cacheTime,.expirationTime(_ expiration: StorageExpiration), \n.none保持原来的过期时间, \n.cacheTime, \n设置过期时间为当前时间加上原来的过期时间, \n.expirationTime(_ expiration: StorageExpiration) 设置过期时间到指定时
    @objc func test231() {
        guard let url = URL(string: "https://picsum.photos/id/70/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        /**
         memoryCacheAccessExtendingExpiration
         当从磁盘中取图片时，往后延长过期时间的策略
         有以下几个值：.none,.cacheTime,.expirationTime(_ expiration: StorageExpiration)
         .none保持原来的过期时间
         .cacheTime 设置过期时间为当前时间加上原来的过期时间
         .expirationTime(_ expiration: StorageExpiration) 设置过期时间到指定时
         */
        let options: KingfisherOptionsInfo = [
            .diskCacheAccessExtendingExpiration(ExpirationExtending.none)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.30、diskCacheExpiration：设置【硬盘】缓存过期时间
    /// diskCacheExpiration：设置【硬盘】缓存过期时间
    @objc func test230() {
        guard let url = URL(string: "https://picsum.photos/id/69/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
    
        let options: KingfisherOptionsInfo = [
            .diskCacheExpiration(StorageExpiration.days(3))
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.29、memoryCacheAccessExtendingExpiration：当从内存中取图片时，往后延长过期时间的策略，有以下几个值：.none,.cacheTime,.expirationTime(_ expiration: StorageExpiration), \n.none保持原来的过期时间, \n.cacheTime, \n设置过期时间为当前时间加上原来的过期时间, \n.expirationTime(_ expiration: StorageExpiration) 设置过期时间到指定时
    /// memoryCacheAccessExtendingExpiration：当从内存、磁盘中取图片时，往后延长过期时间的策略，有以下几个值：.none,.cacheTime,.expirationTime(_ expiration: StorageExpiration), \n.none保持原来的过期时间, \n.cacheTime, \n设置过期时间为当前时间加上原来的过期时间, \n.expirationTime(_ expiration: StorageExpiration) 设置过期时间到指定时
    @objc func test229() {
        guard let url = URL(string: "https://picsum.photos/id/68/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        /**
         memoryCacheAccessExtendingExpiration
         当从内存中取图片时，往后延长过期时间的策略
         有以下几个值：.none,.cacheTime,.expirationTime(_ expiration: StorageExpiration)
         .none保持原来的过期时间
         .cacheTime 设置过期时间为当前时间加上原来的过期时间
         .expirationTime(_ expiration: StorageExpiration) 设置过期时间到指定时
         */
        let options: KingfisherOptionsInfo = [
            .memoryCacheAccessExtendingExpiration(.none)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.28、memoryCacheExpiration：设置【内存】缓存过期时间
    /// memoryCacheExpiration：设置【内存】缓存过期时间
    @objc func test228() {
        guard let url = URL(string: "https://picsum.photos/id/67/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        let options: KingfisherOptionsInfo = [
            .memoryCacheExpiration(StorageExpiration.days(2))
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.27、diskStoreWriteOptions：在将data写入磁盘存储时，关联对象用来设置写入选项
    /// diskStoreWriteOptions：在将data写入磁盘存储时，关联对象用来设置写入选项
    @objc func test227() {
        guard let url = URL(string: "https://picsum.photos/id/66/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        let options: KingfisherOptionsInfo = [
            .diskStoreWriteOptions(Data.WritingOptions.atomic)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.26、loadDiskFileSynchronously：同步加载磁盘图片文件。原本地盘加载图片在自己的异步队列中进行，但是如果imageView已经有图片了，再从磁盘读取image并设置给imageView，会导致闪烁。设置这个option，通常会让所有的加载在UI线程中进行，能防止产生闪烁，但会消耗性能。
    /// loadDiskFileSynchronously：同步加载磁盘图片文件。原本地盘加载图片在自己的异步队列中进行，但是如果imageView已经有图片了，再从磁盘读取image并设置给imageView，会导致闪烁。设置这个option，通常会让所有的加载在UI线程中进行，能防止产生闪烁，但会消耗性能。
    @objc func test226() {
        guard let url = URL(string: "https://picsum.photos/id/65/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        let options: KingfisherOptionsInfo = [
            .loadDiskFileSynchronously
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    
    //MARK: 2.25、alsoPrefetchToMemory：如果使用了ImagePrefetcher，预加载系统会主动将图片加载到内存中。预加载时如果图片已经被存放在磁盘中，从磁盘获取的图片也要缓存到内存中
    /// alsoPrefetchToMemory：如果使用了ImagePrefetcher，预加载系统会主动将图片加载到内存中。预加载时如果图片已经被存放在磁盘中，从磁盘获取的图片也要缓存到内存中
    @objc func test225() {
        guard let url1 = URL(string: "https://picsum.photos/id/62/800/800"), let url2 = URL(string: "https://picsum.photos/id/63/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }

        let urls = [url1, url2]
        let prefetcher = ImagePrefetcher(urls: urls) {
            skippedResources, failedResources, completedResources in
            debugPrint("These resources are prefetched: \(completedResources)")
        }
        prefetcher.start()
        
        let options: KingfisherOptionsInfo = [
            .alsoPrefetchToMemory
        ]
        
        imageView.kf.setImage(with: urls[0], placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.24、onFailureImage：关联属性是个image，当下载或者从缓存中找回图片失败时，会使用这个image填充到ImageView中。适用于不想设置站位图，但是加载图片失败了，又想展示一个设定的图片的情况。
    /// onFailureImage：关联属性是个image，当下载或者从缓存中找回图片失败时，会使用这个image填充到ImageView中。适用于不想设置站位图，但是加载图片失败了，又想展示一个设定的图片的情况。
    @objc func test224() {
        guard let url = URL(string: "https://picsum.photo6") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }

        let options: KingfisherOptionsInfo = [
            .onFailureImage(UIImage(named: "flower2"))
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.23、cacheOriginalImage：缓存原始图片
    /// cacheOriginalImage：缓存原始图片
    @objc func test223() {
        guard let url = URL(string: "https://picsum.photos/id/60/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }

        let options: KingfisherOptionsInfo = [
            .cacheOriginalImage
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.22、onlyLoadFirstFrame：在加载动图时，只加载第一帧。因为加载动图时比较耗费内存的，所以可以加载第一帧作为预览图片。如果不是加载动图，这个option设置了也会被忽略
    /// onlyLoadFirstFrame：onlyLoadFirstFrame：在加载动图时，只加载第一帧。因为加载动图时比较耗费内存的，所以可以加载第一帧作为预览图片。如果不是加载动图，这个option设置了也会被忽略
    @objc func test222() {
        guard let url = URL(string: "http://qq.yh31.com/tp/zjbq/201711092144541829.gif") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }

        let options: KingfisherOptionsInfo = [
            .onlyLoadFirstFrame
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.21、keepCurrentImageWhileLoading：在设置其它图片的过程中（如下载），保留当前图片。设置了这个属性之后，站位图会被忽略。
    /// keepCurrentImageWhileLoading：在设置其它图片的过程中（如下载），保留当前图片。设置了这个属性之后，站位图会被忽略。
    @objc func test221() {
        guard let url = URL(string: "https://picsum.photos/id/57/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }

        let options: KingfisherOptionsInfo = [
            .keepCurrentImageWhileLoading
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.20、imageModifier：在图像加载和显示过程中，将使用你自定义的imageModifier来修改图像。你可以在modify(_:)方法中实现任何你需要的图像修改逻辑，例如调整大小、添加滤镜等操作。
    /// imageModifier：在图像加载和显示过程中，将使用你自定义的imageModifier来修改图像。你可以在modify(_:)方法中实现任何你需要的图像修改逻辑，例如调整大小、添加滤镜等操作。
    @objc func test220() {
        guard let url = URL(string: "https://picsum.photos/id/55/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
    
        // 定义一个转换器，将图片转换为圆形
        let circleModifier = AnyImageModifier { image in
            return image.jk.isCircleImage()!
        }
        let options: KingfisherOptionsInfo = [
            .imageModifier(circleModifier)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.19、cacheSerializer： 缓存序列化
    /// cacheSerializer： 缓存序列化
    @objc func test219() {
        guard let url = URL(string: "https://picsum.photos/id/54/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        let options: KingfisherOptionsInfo = [
            .cacheSerializer(DefaultCacheSerializer.default)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.18、processor：处理器标识符（使用处理器处理图片时，把这个标识传给它）,标识会用来给 key 和 processor 的组合产生一个对应的key）
    /// processor：处理器标识符（使用处理器处理图片时，把这个标识传给它）,标识会用来给 key 和 processor 的组合产生一个对应的key）
    @objc func test218() {
        guard let url = URL(string: "https://picsum.photos/id/53/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        
        // 设置图片模糊效果
        let processor = BlurImageProcessor(blurRadius: 25)
        /** 设置图片圆角
         let options: [KingfisherOptionsInfoItem] = [
             .processor(RoundCornerImageProcessor(cornerRadius: 40))
         ]
         */
        let options: KingfisherOptionsInfo = [
            .processor(processor)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.17、redirectHandler：用来在重定向之前去设置request，比如：我们可以在这里为header添加auth token，url映射等
    /// redirectHandler：用来在重定向之前去设置request，比如：我们可以在这里为header添加auth token，url映射等
    @objc func test217() {
        guard let url = URL(string: "https://picsum.photos/id/51/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
    
        // 定义一个重定向的处理逻辑
        let anyRedirectHandler = AnyRedirectHandler { (task, resp, req, completionHandler) in
            var newReq = req
            newReq.timeoutInterval = 30
            completionHandler(newReq)
        }
        let options: KingfisherOptionsInfo = [
            .redirectHandler(anyRedirectHandler)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.16、requestModifier：发送请求之前用于改变请求。这是最后的机会你可以修改请求。您可以修改请求一些定制的目的,如添加身份验证令牌头,进行基本的HTTP身份验证或类似的url映射。原始请求默认情况下将没有任何修改
    /// requestModifier：发送请求之前用于改变请求。这是最后的机会你可以修改请求。您可以修改请求一些定制的目的,如添加身份验证令牌头,进行基本的HTTP身份验证或类似的url映射。原始请求默认情况下将没有任何修改
    @objc func test216() {
        guard let url = URL(string: "https://picsum.photos/id/50/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        let modifier = AnyModifier { request in
            var r = request
            r.timeoutInterval = 60
            return r
        }
   
        let options: KingfisherOptionsInfo = [
            .requestModifier(modifier)
        ]
        
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.15、preloadAllAnimationData：整个的动图data是否被预加载。默认是不预加载整个的动图数据的，只是按需加载下一帧；如果设置为true，则会将整个动图的data加载并解码到内存中
    /// preloadAllAnimationData：整个的动图data是否被预加载。默认是不预加载整个的动图数据的，只是按需加载下一帧；如果设置为true，则会将整个动图的data加载并解码到内存中
    @objc func test215() {
        guard let url = URL(string: "https://picsum.photos/id/49/800/800") else {
            return
        }
        
        let imageView = CustomImageView()
        self.view.addSubview(imageView)
        
        imageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
       
        let options: [KingfisherOptionsInfoItem] = [
            .preloadAllAnimationData
        ]
        imageView.kf.setImage(with: url, placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView, result: result)
        }
    }
    
    //MARK: 2.14、scaleFactor：将检索到的图片数据转换成一个图时 这个成员变量将被用作图片缩放因子。图像分辨率,而不是屏幕尺寸。你可能处理时需要指定正确的缩放因子@2x或@3x Retina图像。
    /// scaleFactor：将检索到的图片数据转换成一个图时 这个成员变量将被用作图片缩放因子。图像分辨率,而不是屏幕尺寸。你可能处理时需要指定正确的缩放因子@2x或@3x Retina图像。
    @objc func test214() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        // 设置缩放因子
        let scaleFactor: CGFloat = jk_kPixel
        let options: [KingfisherOptionsInfoItem] = [
            .scaleFactor(scaleFactor)
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/48/800/800"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.13、callbackQueue：是一个用于指定回调闭包执行的队列。它定义了在完成图片加载或其他操作后，回调闭包在哪个队列中执行。默认情况下，Kingfisher 会在主队列（Main Queue）中执行回调闭包，以确保在主线程上更新 UI。但是，您可以通过设置 `callbackQueue` 来自定义回调闭包的执行队列。
    /// callbackQueue：是一个用于指定回调闭包执行的队列。它定义了在完成图片加载或其他操作后，回调闭包在哪个队列中执行。默认情况下，Kingfisher 会在主队列（Main Queue）中执行回调闭包，以确保在主线程上更新 UI。但是，您可以通过设置 `callbackQueue` 来自定义回调闭包的执行队列。
    @objc func test213() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        
        // 创建一个并发队列
        let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
        
        // 在队列中执行任务
        concurrentQueue.async {
            // 在此执行需要在队列中完成的任务
            debugPrint("Task 1 executed on concurrent queue ✅")
        }
        
        concurrentQueue.async {
            // 在此执行另一个需要在队列中完成的任务
            debugPrint("Task 2 executed on concurrent queue✅")
        }
        
        let options: [KingfisherOptionsInfoItem] = [
            .callbackQueue(.dispatch(concurrentQueue))
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/46/800/800"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            debugPrint("当前的线程：\(Thread.current)")
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.12、onlyFromCache：强制从缓存中获取图片（缓存中没有也不会从网络下载）
    /// onlyFromCache：强制从缓存中获取图片（缓存中没有也不会从网络下载）
    @objc func test212() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let options: [KingfisherOptionsInfoItem] = [
            .backgroundDecode
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/42/800/800"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.11、onlyFromCache：强制从缓存中获取图片（缓存中没有也不会从网络下载）
    /// onlyFromCache：强制从缓存中获取图片（缓存中没有也不会从网络下载）
    @objc func test211() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let options: [KingfisherOptionsInfoItem] = [
            .onlyFromCache
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/42/800/800"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.10、waitForCache：等待缓存结束后在执行completionBlock
    /// waitForCache：等待缓存结束后在执行completionBlock
    @objc func test210() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let options: [KingfisherOptionsInfoItem] = [
            .waitForCache
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/41/800/800"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }

    //MARK: 2.09、cacheMemoryOnly：只将图片存储在缓存中
    /// cacheMemoryOnly：只将图片存储在缓存中
    @objc func test209() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let options: [KingfisherOptionsInfoItem] = [
            .cacheMemoryOnly
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/40/800/800"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.08、forceTransition：每次都要执行翻转动画
    /// forceTransition：每次都要执行翻转动画
    @objc func test208() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let options: [KingfisherOptionsInfoItem] = [
            .forceTransition
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/39/800/800"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) {[weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.07、fromMemoryCacheOrRefresh：如果设置了这个选项，Kingfisher会优先从缓存中找回图片，如果在缓存中没找到，则会直接开始重新下载，不会再去磁盘中进行查找。当我们的图片资源发生变化，但是图片的url并没有发生变化时，可使用该选项，能有效防止多次重复下载。
    /// fromMemoryCacheOrRefresh：如果设置了这个选项，Kingfisher会优先从缓存中找回图片，如果在缓存中没找到，则会直接开始重新下载，不会再去磁盘中进行查找。当我们的图片资源发生变化，但是图片的url并没有发生变化时，可使用该选项，能有效防止多次重复下载。
    @objc func test207() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let options: [KingfisherOptionsInfoItem] = [
            .fromMemoryCacheOrRefresh
        ]
    
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/38/800/800"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) {[weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.06、forceRefresh：忽略本地缓存，重新下载
    /// forceRefresh：忽略本地缓存，重新下载
    @objc func test206() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let options: [KingfisherOptionsInfoItem] = [
            .forceRefresh
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/37/800/800"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) {[weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.05、downloadPriority：设置该图片下载的优先级，值的范围是0 - 1，如果没有设置这个选项，则默认使用URLSessionTask.defaultPriority
    /// downloadPriority：设置该图片下载的优先级，值的范围是0 - 1，如果没有设置这个选项，则默认使用URLSessionTask.defaultPriority
    @objc func test205() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let options: [KingfisherOptionsInfoItem] = [
            .downloadPriority(0.3)
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/36/800/800"), placeholder: UIImage.jk.image(color: UIColor.randomColor), options: options) {[weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.04、transition：如果该图片是需要从网络下载的，Kingfisher会根据设置的ImageTransition枚举对象来对这个图片进行动画。如果是从缓存或者磁盘中找回的，则无动画。如果想要每次都执行动画，则需要设置.forceRefresh选项，使图片永远是下载的；或者使用.forceTransition，哪怕是从缓存中获取的图片，也会执行动画。
    /// transition：如果该图片是需要从网络下载的，Kingfisher会根据设置的ImageTransition枚举对象来对这个图片进行动画。如果是从缓存或者磁盘中找回的，则无动画。如果想要每次都执行动画，则需要设置.forceRefresh选项，使图片永远是下载的；或者使用.forceTransition，哪怕是从缓存中获取的图片，也会执行动画。
    @objc func test204() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.height.equalTo(jk_kScreenW - 32)
        }
        let options: [KingfisherOptionsInfoItem] = [
            .transition(.fade(0.3))
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/35/800/800"), placeholder: UIImage.jk.image(color: UIColor.randomColor), options: options) {[weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.03、downloader：指定用来下载图片的ImageDownloader对象
    /// downloader：这个成员的关联值应该是一个ImageDownloader对象。Kingfisher将使用这个下载器下载的图片。
    @objc func test203() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(CGSize(width: 200, height: 200))
        }
        let options: [KingfisherOptionsInfoItem] = [
            .downloader(ImageDownloader(name: "jj"))
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/34/600/600"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) {[weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.02、指定被处理后图片缓存
    /// 指定kingfisher使用的cache对象，会被用来找回缓存的图片和存储下载的图片。
    @objc func test202() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(CGSize(width: 200, height: 200))
        }
        let targetCache = ImageCache(name: "target_Cache")
        let options: [KingfisherOptionsInfoItem] = [
            .targetCache(targetCache)
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/32/600/600"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) {[weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 2.01、指定原始图片缓存
    /*
     用来存储初始图片的cache对象，会被用来找回缓存的图片和存储下载的初始图片，如果没有设置originalCache(ImageCache)，则会去targetCache(ImageCache)对象中查找初始图片。
     在下载和存储图片的过程中，如果设置了cacheOriginalImage这个option，就会将初始图片缓存在originalCache中；如果在targetCache对象中没有找到经过professor处理的图片，则会去originCache中查找原始图片，如果在originalCache中找到了原始图片，则会将该图片根据professor进行处理。这样能有效防止重复下载图片。
     */
    @objc func test201() {
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(CGSize(width: 200, height: 200))
        }
        let originalCache = ImageCache(name: "original_Cache")
        let options: [KingfisherOptionsInfoItem] = [
            .originalCache(originalCache)
        ]
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/30/600/600"), placeholder:  UIImage.jk.image(color: UIColor.randomColor), options: options) {[weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
}

//MARK: - 一、基本的使用
extension ViewController {
    //MARK: 1.02、显示加载的进度
    @objc func test102() {
        guard let url =  URL(string: "https://picsum.photos/id/29/1000/1000") else {
            return
        }
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        
        imageView1.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.size.equalTo(CGSize(width: jk_kScreenW - 32, height: jk_kScreenW - 32))
        }
        /**参数解释
         /// - Parameters:
         ///   - resource: 一个URL遵守的Protocol，一般传入图片的URL，不可为空
         ///   - placeholder: 是一个默认的占位图，可为空
         ///   - options: 是个枚举数组，配置Kingfisher下载图片的一些操作行为
         ///   - progressBlock:  是个下载进度闭包，可以用于更新下载UI
         ///   - completionHandler: 是个下载完成闭包，闭包参数包含图片，错误，缓存类型，URL 信息
         /// - Returns: A task represents the image downloading.
         */
        imageView1.kf.setImage(with: url, placeholder: UIImage.jk.image(color: UIColor.randomColor)) { [weak self] receivedSize, totalSize in
            guard let self else { return }
            self.progressEvent(receivedSize: receivedSize, totalSize: totalSize)
        } completionHandler: { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
    }
    
    //MARK: 1.01、常规加载图片
    @objc func test101() {
        /**
         用直接设置的方法，Kingfisher首先会尝试从缓存中去取，如果没有，则直接下载图片并且缓存下来以备后用。此外，Kingfisher默认使用absoluteString of url(即绝对url）作为cacheKey以方便再次加载该图片的时候去缓存中根据cacheKey(也就是图片url）查找，通俗来说就是把图片的整个链接作为cacheKey来缓存在本地。
         */
        let imageView1 = CustomImageView()
        self.view.addSubview(imageView1)
        imageView1.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.centerY.equalToSuperview().offset(-250)
            make.size.equalTo(CGSize(width: 250, height: 250))
        }

        let imageView2 = CustomImageView()
        self.view.addSubview(imageView2)
        
        imageView2.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.top.equalTo(imageView1.snp.bottom).offset(30)
            make.size.equalTo(CGSize(width: 250, height: 250))
        }
        
        imageView1.kf.setImage(with: URL(string: "https://picsum.photos/id/28/1000/1000"), placeholder: UIImage.jk.image(color: UIColor.randomColor)) { [weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView1, result: result)
        }
        imageView2.kf.setImage(with: URL(string: "https://picsum.photos/id/27/1000/1000"), placeholder: UIImage.jk.image(color: UIColor.randomColor)) {[weak self] result in
            guard let self else { return }
            self.resultEvent(imageView: imageView2, result: result)
        }
    }
}

//MARK: - 结果处理
extension ViewController {
    
    /// 下载进度
    /// - Parameters:
    ///   - receivedSize: 下载的大小
    ///   - totalSize: 总大小
    private func progressEvent(receivedSize: Int64, totalSize: Int64) {
        debugPrint("图片进度：下载的大小：\(receivedSize) 总大小：\(totalSize)")
    }
    
    /// 结果处理
    /// - Parameters:
    ///   - imageView: imageView description
    ///   - result: result description
    private func resultEvent(imageView: UIImageView, result: Result<RetrieveImageResult, KingfisherError>) {
        JKAsyncs.asyncDelay(3) {
        } _: {
            imageView.removeFromSuperview()
        }
        switch result {
        case .success(let retrieveImageResult):
            // 删除指定键的图片
            // ImageCache.default.removeImage(forKey: url.absoluteString, fromMemory: true)
            /*
             image：下载后的对象
             cacheType：缓存类型，是个枚举，分以下三种：
                 .none   图片还没缓存（也就是第一次加载图片的时候）
                 .memory 从内存中获取到的缓存图片（第二次及以上加载）
                 .disk   从磁盘中获取到的缓存图片（第二次及以上加载）
             imageUrl：所要下载的图片的url
             */
            debugPrint("✅加载成功：\(retrieveImageResult)")
            /*
            retrieveImageResult.image.jk.savePhotosImageToAlbum { isSuccess, error in
                debugPrint("保存到相册：\(isSuccess ? "✅" : "❌")")
            }
             */
        case .failure(let error):
            debugPrint("❌加载失败：\(error)")
        }
    }
    
    //MARK: 信息提示
    /// 信息提示
    /// - Parameter message: 内容
    private func toastMessage(_ message: String) {
        DispatchQueue.main.async {
            self.view.makeToast(message, position: .center)
        }
    }
}

//MARK: - 自定义UIImageView
class CustomImageView: UIImageView {
    
    override init(frame: CGRect = CGRect.zero) {
        super.init(frame: frame)
        kf.indicatorType = .activity
        contentMode = .scaleAspectFill
        clipsToBounds = true
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
